{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "7c300398",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel, model_validator\n",
    "# 官方文档 https://pydantic.com.cn/concepts/models/"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "287a914f",
   "metadata": {},
   "source": [
    "# 1. python原生写类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "3965f772",
   "metadata": {},
   "outputs": [],
   "source": [
    "# python原生写法\n",
    "class User:\n",
    "    def __init__(self, name:str, age: int):\n",
    "        self.name = name\n",
    "        self.age = age"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "8998b6bb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice\n",
      "25\n"
     ]
    }
   ],
   "source": [
    "user1 = User(name=\"Alice\", age=25)\n",
    "print(user1.name)  # 输出: Alice\n",
    "print(user1.age)  # 输出: Alice"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "02e58b37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice\n",
      "25\n"
     ]
    }
   ],
   "source": [
    "user2 = User(name=12123, age=\"25\")  # age应为int，传入str会直接报错\n",
    "print(user1.name)  # 输出: Alice # 对name传入str不做校验\n",
    "print(user1.age)  # 输出: Alice"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3baac739",
   "metadata": {},
   "source": [
    "# 2. 使用pydantic的BaseModel定义字段"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "660c5955",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice\n",
      "25\n"
     ]
    }
   ],
   "source": [
    "from pydantic import BaseModel\n",
    "\n",
    "class User(BaseModel):\n",
    "    name: str  # 必须是字符串\n",
    "    age: int   # 必须是整数\n",
    "\n",
    "# 正确的情况\n",
    "user1 = User(name=\"Alice\", age=25)\n",
    "print(user1.name)  # 输出: Alice\n",
    "print(user1.age)  # 输出: Alice"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "29163a0a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "报错啦：1 validation error for User\n",
      "name\n",
      "  Input should be a valid string [type=string_type, input_value=12123, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/string_type, traceback:Traceback (most recent call last):\n",
      "  File \"/tmp/ipykernel_87794/2433916875.py\", line 4, in <module>\n",
      "    user2 = User(name=12123, age=\"25\")  # age应为int，传入str会直接报错\n",
      "  File \"/d/github/langchain_course/.venv/lib/python3.10/site-packages/pydantic/main.py\", line 250, in __init__\n",
      "    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)\n",
      "pydantic_core._pydantic_core.ValidationError: 1 validation error for User\n",
      "name\n",
      "  Input should be a valid string [type=string_type, input_value=12123, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/string_type\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import traceback # 用来捕捉报错调用堆栈, 很常用的捕捉异常的包\n",
    "# 错误的情况（自动报错）\n",
    "try:\n",
    "    user2 = User(name=12123, age=\"25\")  # age应为int，传入str会直接报错\n",
    "    print(user2.name)\n",
    "    print(user2.age)\n",
    "except Exception as e:\n",
    "    print(f\"报错啦：{str(e)}, traceback:{traceback.format_exc()}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "e152c80d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice\n",
      "25\n"
     ]
    }
   ],
   "source": [
    "# 错误的情况，居然没报错。\n",
    "try:\n",
    "    user2 = User(name=\"Alice\", age=\"25\")  # age应为int，与pydantic的版本有关\n",
    "    print(user2.name)\n",
    "    print(user2.age)\n",
    "except Exception as e:\n",
    "    print(f\"报错啦：{str(e)}, traceback:{traceback.format_exc()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbd87224",
   "metadata": {},
   "source": [
    "# 3. 增加Config配置类 进行强制校验"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "id": "41e802aa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "报错啦：1 validation error for User\n",
      "age\n",
      "  Input should be a valid integer [type=int_type, input_value='25', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/int_type, traceback:Traceback (most recent call last):\n",
      "  File \"/tmp/ipykernel_87794/2711582258.py\", line 11, in <module>\n",
      "    user2 = User(name=\"Alice\", age=\"25\")  # age应为int，与pydantic的版本有关\n",
      "  File \"/d/github/langchain_course/.venv/lib/python3.10/site-packages/pydantic/main.py\", line 250, in __init__\n",
      "    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)\n",
      "pydantic_core._pydantic_core.ValidationError: 1 validation error for User\n",
      "age\n",
      "  Input should be a valid integer [type=int_type, input_value='25', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/int_type\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_87794/2711582258.py:4: PydanticDeprecatedSince20: Support for class-based `config` is deprecated, use ConfigDict instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.12/migration/\n",
      "  class User(BaseModel):\n"
     ]
    }
   ],
   "source": [
    "import traceback # 用来捕捉报错调用堆栈, 很常用的捕捉异常的包\n",
    "from pydantic import BaseModel\n",
    "\n",
    "class User(BaseModel):\n",
    "    name: str  # 必须是字符串\n",
    "    age: int   # 必须是整数\n",
    "    # 增加Config配置类\n",
    "    class Config:\n",
    "        strict = True  # 开启严格模式，禁止自动类型转换\n",
    "try:\n",
    "    user2 = User(name=\"Alice\", age=\"25\")  # age应为int，与pydantic的版本有关\n",
    "    print(user2.name)\n",
    "    print(user2.age)\n",
    "except Exception as e:\n",
    "    print(f\"报错啦：{str(e)}, traceback:{traceback.format_exc()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63d96d1f",
   "metadata": {},
   "source": [
    "# 4. 使用@field_validator进行字段自定义校验"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "bee5a9d7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "报错啦：1 validation error for User\n",
      "age\n",
      "  Value error, 年龄必须为正数 [type=value_error, input_value=-5, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error, traceback:Traceback (most recent call last):\n",
      "  File \"/tmp/ipykernel_87794/1036719035.py\", line 17, in <module>\n",
      "    User(name=\"Alice\", age=-5)  # 抛出 ValueError: 年龄必须为正数\n",
      "  File \"/d/github/langchain_course/.venv/lib/python3.10/site-packages/pydantic/main.py\", line 250, in __init__\n",
      "    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)\n",
      "pydantic_core._pydantic_core.ValidationError: 1 validation error for User\n",
      "age\n",
      "  Value error, 年龄必须为正数 [type=value_error, input_value=-5, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from pydantic import BaseModel, field_validator\n",
    "\n",
    "# 既然要校验字段\n",
    "class User(BaseModel):\n",
    "    name: str\n",
    "    age: int\n",
    "    # 字段级别的校验\n",
    "    @field_validator(\"age\")\n",
    "    def age_must_be_positive(cls, v):\n",
    "        if v < 0:\n",
    "            raise ValueError(\"年龄必须为正数\")\n",
    "        return v\n",
    "\n",
    "# 校验失败：age为负数\n",
    "User(name=\"Alice\", age=10)  # 抛出 ValueError: 年龄必须为正数\n",
    "try:\n",
    "    User(name=\"Alice\", age=-5)  # 抛出 ValueError: 年龄必须为正数\n",
    "except Exception as e:\n",
    "    print(f\"报错啦：{str(e)}, traceback:{traceback.format_exc()}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7daf7999",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel, field_validator, ValidationError\n",
    "\n",
    "class User(BaseModel):\n",
    "    name: str\n",
    "    age: int\n",
    "\n",
    "    # 校验 age 必须为正数且小于 150（扩展你的示例）\n",
    "    @field_validator(\"age\")\n",
    "    def validate_age(cls, v):\n",
    "        if v < 0:\n",
    "            raise ValueError(\"年龄必须为正数\")\n",
    "        if v > 150:\n",
    "            raise ValueError(\"年龄不能超过 150 岁\")\n",
    "        return v  # 校验通过后返回值（可原样返回或转换）\n",
    "\n",
    "    # 校验 name 不能为空字符串\n",
    "    @field_validator(\"name\")\n",
    "    def validate_name(cls, v):\n",
    "        if not v.strip():  # 排除纯空格的情况\n",
    "            raise ValueError(\"姓名不能为空\")\n",
    "        return v.title()  # 自动转换为首字母大写（如 \"alice\" → \"Alice\"）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "d4493b81",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2 validation errors for User\n",
      "name\n",
      "  Value error, 姓名不能为空 [type=value_error, input_value='  ', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "age\n",
      "  Value error, 年龄必须为正数 [type=value_error, input_value=-5, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n"
     ]
    }
   ],
   "source": [
    "# 测试校验\n",
    "try:\n",
    "    User(name=\"  \", age=-5)  # 姓名为空、年龄为负，触发错误\n",
    "except ValidationError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "d2086339",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "报错啦: 2 validation errors for User\n",
      "name\n",
      "  Value error, 姓名不能为空 [type=value_error, input_value='  ', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "age\n",
      "  Value error, 年龄不能超过 150 岁 [type=value_error, input_value=156, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error, traceback: Traceback (most recent call last):\n",
      "  File \"/tmp/ipykernel_87794/1781959624.py\", line 2, in <module>\n",
      "    User(name=\"  \", age=156)  # 姓名为空、大于150，触发错误\n",
      "  File \"/d/github/langchain_course/.venv/lib/python3.10/site-packages/pydantic/main.py\", line 250, in __init__\n",
      "    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)\n",
      "pydantic_core._pydantic_core.ValidationError: 2 validation errors for User\n",
      "name\n",
      "  Value error, 姓名不能为空 [type=value_error, input_value='  ', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "age\n",
      "  Value error, 年龄不能超过 150 岁 [type=value_error, input_value=156, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    User(name=\"  \", age=156)  # 姓名为空、大于150，触发错误\n",
    "except Exception as e:\n",
    "    print(f\"报错啦: {str(e)}, traceback: {traceback.format_exc()}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "03275db3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice 145\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    user_instance = User(name=\"Alice\", age=\"145\")  # age是字符串145, 居然没报错，那又该怎么办\n",
    "    print(user_instance.name, user_instance.age)\n",
    "except Exception as e:\n",
    "    print(f\"报错啦: {str(e)}, traceback: {traceback.format_exc()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77b7e78b",
   "metadata": {},
   "source": [
    "# 5. 使用Field字段配合校验\n",
    "直接通过字段类型 + 参数限制，无需自定义函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "f309ce70",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3 validation errors for User\n",
      "name\n",
      "  String should have at least 2 characters [type=string_too_short, input_value='A', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/string_too_short\n",
      "age\n",
      "  Input should be greater than 0 [type=greater_than, input_value=-5, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/greater_than\n",
      "score\n",
      "  Input should be less than or equal to 100 [type=less_than_equal, input_value=101, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/less_than_equal\n"
     ]
    }
   ],
   "source": [
    "from pydantic import BaseModel, Field, PositiveInt\n",
    "\n",
    "class User(BaseModel):\n",
    "    # 最小长度为2，最大长度为20\n",
    "    name: str = Field(min_length=2, max_length=20, description=\"用户名2-20字\")\n",
    "    age: PositiveInt = Field(description=\"必须是正整数\")  # 拒绝负数/0\n",
    "    score: float = Field(ge=0, le=100, description=\"分数0-100\")  # ge=≥, le=≤\n",
    "\n",
    "# 测试：不满足约束会抛 ValidationError\n",
    "try:\n",
    "    User(name=\"A\", age=-5, score=101)\n",
    "except Exception as e:\n",
    "    print(e)  # 会列出4个错误：name长度、age负数、email格式、score超界"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38d90336",
   "metadata": {},
   "source": [
    "# 6. 模型级别的校验@model_validator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "id": "345fe208",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel, model_validator, ValidationError\n",
    "\n",
    "class User(BaseModel):\n",
    "    name: str\n",
    "    age: int\n",
    "    job: str | None = None  # 职业，可选字段\n",
    "\n",
    "    # 模型级校验：关联 age 和 job 字段\n",
    "    @model_validator(mode='after')  # mode='after' 表示所有字段先通过自身校验，再执行此逻辑\n",
    "    def check_job_based_on_age(self):\n",
    "        # 获取已校验的字段值（self 即模型实例）\n",
    "        age = self.age\n",
    "        job = self.job\n",
    "\n",
    "        # 成年人必须有职业\n",
    "        if age >= 18 and job is None:\n",
    "            raise ValueError(\"成年人（≥18岁）必须填写职业\")\n",
    "        \n",
    "        # 未成年人不能有职业\n",
    "        if age < 18 and job is not None:\n",
    "            raise ValueError(\"未成年人（<18岁）无需填写职业\")\n",
    "        \n",
    "        return self  # 校验通过后返回模型实例\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "id": "b56fd2e9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 validation error for User\n",
      "  Value error, 成年人（≥18岁）必须填写职业 [type=value_error, input_value={'name': '张三', 'age': 25, 'job': None}, input_type=dict]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n"
     ]
    }
   ],
   "source": [
    "# 测试 1：成年人未填职业 → 报错\n",
    "try:\n",
    "    User(name=\"张三\", age=25, job=None)\n",
    "except ValidationError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "d3c1467f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 validation error for User\n",
      "  Value error, 未成年人（<18岁）无需填写职业 [type=value_error, input_value={'name': '李四', 'age': 15, 'job': '学生'}, input_type=dict]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n"
     ]
    }
   ],
   "source": [
    "# 测试 2：未成年人填写了职业 → 报错\n",
    "try:\n",
    "    User(name=\"李四\", age=15, job=\"学生\")\n",
    "except ValidationError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "id": "a2fc418a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "校验通过的用户：\n",
      "name='王五' age=30 job='工程师'\n",
      "name='赵六' age=17 job=None\n"
     ]
    }
   ],
   "source": [
    "# 测试 3：成年人填写职业，未成年人未填 → 正常通过\n",
    "user1 = User(name=\"王五\", age=30, job=\"工程师\")\n",
    "user2 = User(name=\"赵六\", age=17, job=None)\n",
    "print(\"校验通过的用户：\", user1, user2, sep=\"\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "id": "6754559a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 validation error for User\n",
      "  Value error, 12岁以下用户，用户名长度不能超过5个字符 [type=value_error, input_value={'name': 'AliceSmith', 'age': 10}, input_type=dict]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "1 validation error for User\n",
      "  Value error, 12岁及以上用户，用户名长度不能少于3个字符 [type=value_error, input_value={'name': 'Li', 'age': 15}, input_type=dict]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "校验通过： name='Ann' age=8\n"
     ]
    }
   ],
   "source": [
    "from pydantic import BaseModel, model_validator, ValidationError\n",
    "\n",
    "class User(BaseModel):\n",
    "    name: str\n",
    "    age: int\n",
    "\n",
    "    @model_validator(mode='after')\n",
    "    def check_name_length_based_on_age(self):\n",
    "        name_len = len(self.name)\n",
    "        age = self.age\n",
    "\n",
    "        if age < 12:\n",
    "            if name_len > 5:\n",
    "                raise ValueError(\"12岁以下用户，用户名长度不能超过5个字符\")\n",
    "        else:\n",
    "            if name_len < 3:\n",
    "                raise ValueError(\"12岁及以上用户，用户名长度不能少于3个字符\")\n",
    "        \n",
    "        return self\n",
    "\n",
    "# 测试 1：10岁用户，用户名过长 → 报错\n",
    "try:\n",
    "    User(name=\"AliceSmith\", age=10)\n",
    "except ValidationError as e:\n",
    "    print(e)\n",
    "\n",
    "# 测试 2：15岁用户，用户名过短 → 报错\n",
    "try:\n",
    "    User(name=\"Li\", age=15)\n",
    "except ValidationError as e:\n",
    "    print(e)\n",
    "\n",
    "# 测试 3：符合条件的用户 → 正常通过\n",
    "user = User(name=\"Ann\", age=8)  # 8岁，用户名3个字符（≤5）\n",
    "print(\"校验通过：\", user)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "id": "13204f5b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 validation error for Order\n",
      "  Value error, 结束日期(2024-01-01)不能早于开始日期(2024-12-31) [type=value_error, input_value={'start_date': '2024-12-3...end_date': '2024-01-01'}, input_type=dict]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_87794/2684733604.py:10: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.\n",
      "  def end_date_after_start_date(cls, obj):\n"
     ]
    }
   ],
   "source": [
    "# 再举例\n",
    "from pydantic import BaseModel, model_validator\n",
    "\n",
    "class Order(BaseModel):\n",
    "    start_date: str  # 简化为字符串日期，实际可用 date 类型\n",
    "    end_date: str\n",
    "\n",
    "    # mode=\"after\"：所有字段先验证完成再执行（默认）\n",
    "    @model_validator(mode=\"after\")\n",
    "    def end_date_after_start_date(cls, obj):\n",
    "        if obj.start_date > obj.end_date:\n",
    "            raise ValueError(f\"结束日期({obj.end_date})不能早于开始日期({obj.start_date})\")\n",
    "        return obj\n",
    "\n",
    "# 错误示例\n",
    "try:\n",
    "    Order(start_date=\"2024-12-31\", end_date=\"2024-01-01\")\n",
    "except Exception as e:\n",
    "    print(e)  # 触发模型级验证错误"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f1d1961",
   "metadata": {},
   "source": [
    "# 7. 作用初识"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e74078dd",
   "metadata": {},
   "source": [
    "## 7.1 数据转换验证（自动类型转换）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "id": "53aaa9bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name='100' age=10\n",
      "type(data.name):  <class 'str'>\n",
      "type(data.age):  <class 'int'>\n"
     ]
    }
   ],
   "source": [
    "from pydantic import BaseModel, model_validator, ValidationError\n",
    "\n",
    "class User(BaseModel):\n",
    "    name: str\n",
    "    age: int\n",
    "    \n",
    "    @model_validator(mode='after')\n",
    "    def check_name_length_based_on_age(self):\n",
    "        name_len = len(self.name)\n",
    "        age = self.age\n",
    "\n",
    "        if age < 12:\n",
    "            if name_len > 5:\n",
    "                raise ValueError(\"12岁以下用户，用户名长度不能超过5个字符\")\n",
    "        else:\n",
    "            if name_len < 3:\n",
    "                raise ValueError(\"12岁及以上用户，用户名长度不能少于3个字符\")\n",
    "        \n",
    "        return self\n",
    "# 自动转换：字符串转int、数字转bool\n",
    "data = User(name=\"100\", age=\"10\")\n",
    "print(data)\n",
    "print(\"type(data.name): \", type(data.name))\n",
    "print(\"type(data.age): \", type(data.age))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30885a67",
   "metadata": {},
   "source": [
    "## 7.2 批量数据校验"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "id": "1f835e59",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据 {'name': '赵五', 'age': 8} 验证通过\n",
      "数据 {'name': '钱六六六六六', 'age': 11} 验证失败：Value error, 12岁以下用户，用户名长度不能超过5个字符\n",
      "数据 {'name': '孙', 'age': 18} 验证失败：Value error, 12岁及以上用户，用户名长度不能少于3个字符\n"
     ]
    }
   ],
   "source": [
    "users_data = [\n",
    "    {\"name\": \"赵五\", \"age\": 8},    # 名字长度2（<12岁，允许，正确）\n",
    "    {\"name\": \"钱六六六六六\", \"age\": 11},  # 名字长度6（<12岁，超长，错误）\n",
    "    {\"name\": \"孙\", \"age\": 18},     # 名字长度1（≥12岁，过短，错误）\n",
    "]\n",
    "\n",
    "for data in users_data:\n",
    "    try:\n",
    "        User(** data)\n",
    "        print(f\"数据 {data} 验证通过\")\n",
    "    except ValidationError as e:\n",
    "        print(f\"数据 {data} 验证失败：{e.errors()[0]['msg']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ba88145",
   "metadata": {},
   "source": [
    "## 7.3 转化为字典/json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "id": "763f917f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "User 实例创建成功\n",
      "转换后的字典：\n",
      "{'name': '张三三', 'age': 10}\n",
      "类型： <class 'dict'>\n"
     ]
    }
   ],
   "source": [
    "# 1. 创建一个合法的 User 实例\n",
    "try:\n",
    "    user = User(name=\"张三三\", age=10)  # 10岁，名字长度3（符合规则）\n",
    "    print(\"User 实例创建成功\")\n",
    "except ValidationError as e:\n",
    "    print(f\"创建失败：{e}\")\n",
    "# 2. 转换为字典\n",
    "user_dict = user.model_dump()  # 推荐使用 model_dump()（Pydantic v2 标准方法）\n",
    "# 或使用 user.dict()（兼容旧版本，功能相同）\n",
    "\n",
    "print(\"转换后的字典：\")\n",
    "print(user_dict)\n",
    "print(\"类型：\", type(user_dict))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "id": "3a2cb10d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "转换后的 JSON 字符串：\n",
      "{\"name\":\"张三三\",\"age\":10}\n",
      "类型： <class 'str'>\n"
     ]
    }
   ],
   "source": [
    "# 2. 转换为 JSON 字符串\n",
    "# 方法1：Pydantic v2 推荐的 model_dump_json()\n",
    "user_json = user.model_dump_json()\n",
    "\n",
    "# 方法2：兼容旧版本的 json()（功能相同）\n",
    "# user_json = user.json()\n",
    "\n",
    "print(\"转换后的 JSON 字符串：\")\n",
    "print(user_json)\n",
    "print(\"类型：\", type(user_json))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ec90d1b",
   "metadata": {},
   "source": [
    "## 7.4 重要！转为jsonschema 用于大模型字段选择"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 130,
   "id": "47159e86",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"properties\": {\n",
      "    \"name\": {\n",
      "      \"title\": \"Name\",\n",
      "      \"type\": \"string\"\n",
      "    },\n",
      "    \"age\": {\n",
      "      \"title\": \"Age\",\n",
      "      \"type\": \"integer\"\n",
      "    }\n",
      "  },\n",
      "  \"required\": [\n",
      "    \"name\",\n",
      "    \"age\"\n",
      "  ],\n",
      "  \"title\": \"User\",\n",
      "  \"type\": \"object\"\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "# 生成 JSON Schema\n",
    "user_json_schema = User.model_json_schema()\n",
    "\n",
    "# 打印结果（格式化输出便于阅读）\n",
    "import json\n",
    "print(json.dumps(user_json_schema, indent=2, ensure_ascii=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac46fd50",
   "metadata": {},
   "source": [
    "# 8. 初步的综合举例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b77d95db",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel, field_validator, ValidationInfo\n",
    "# 再举例\n",
    "class Product(BaseModel):\n",
    "    price: float\n",
    "    discount_price: float\n",
    "\n",
    "    # 验证折扣价不能高于原价,针对discount_price这个字段进行校验\n",
    "    @field_validator(\"discount_price\")\n",
    "    def discount_not_higher_than_price(cls, v: float, info: ValidationInfo):  # 使用 ValidationInfo 获取其他字段\n",
    "        price = info.data.get('price')  # 从 info.data 中获取已验证的 price\n",
    "        if price is not None and v > price:\n",
    "            raise ValueError(f\"折扣价({v})不能高于原价({price})\")\n",
    "        return v\n",
    "\n",
    "    # 预处理：价格保留2位小数，针对price字段和discount_price字段进行校验。\n",
    "    @field_validator(\"price\", \"discount_price\", mode=\"before\")\n",
    "    def round_to_two_decimals(cls, v):\n",
    "        return round(float(v), 2)\n",
    "\n",
    "# 正确示例\n",
    "product = Product(price=99.999, discount_price=89.99)\n",
    "print(product)  # 输出: price=100.0, discount_price=89.99"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8237a7e7",
   "metadata": {},
   "source": [
    "# 9. 复杂场景：嵌套模型 + 枚举验证"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "id": "f25a466b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "order_id='ORD-123456' address=Address(province='北京', city='朝阳') payment_method=<PaymentMethod.WECHAT: 'wechat'>\n"
     ]
    }
   ],
   "source": [
    "from pydantic import BaseModel\n",
    "from enum import Enum\n",
    "\n",
    "class PaymentMethod(str, Enum):\n",
    "    WECHAT = \"wechat\"\n",
    "    ALIPAY = \"alipay\"\n",
    "\n",
    "class Address(BaseModel):\n",
    "    province: str\n",
    "    city: str = Field(min_length=2, description=\"城市名至少2字\")\n",
    "\n",
    "class OrderWithDetail(BaseModel):\n",
    "    order_id: str = Field(pattern=r\"^ORD-\\d{6}$\", description=\"订单号格式：ORD-6位数字\")\n",
    "    address: Address  # 嵌套模型验证\n",
    "    payment_method: PaymentMethod  # 只能是枚举中的值\n",
    "\n",
    "# 正确示例\n",
    "valid_order = OrderWithDetail(\n",
    "    order_id=\"ORD-123456\",\n",
    "    address={\"province\": \"北京\", \"city\": \"朝阳\"},\n",
    "    payment_method=\"wechat\"\n",
    ")\n",
    "print(valid_order)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "id": "6ee52773",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ 验证通过结果：\n",
      "{'username': 'user_123', 'age': 25, 'email': 'user@example.com', 'score': 95.7, 'gender': <Gender.MALE: 'male'>, 'address': {'city': '北京', 'zipcode': '100000'}}\n",
      "\n",
      "❌ 验证失败示例（触发多类错误）：\n",
      "7 validation errors for AllInOneValidation\n",
      "username\n",
      "  String should have at least 3 characters [type=string_too_short, input_value='u!', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/string_too_short\n",
      "age\n",
      "  Input should be less than or equal to 120 [type=less_than_equal, input_value=150, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/less_than_equal\n",
      "email\n",
      "  Value error, 邮箱格式无效：invalid-email [type=value_error, input_value='invalid-email', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "score\n",
      "  Input should be greater than 0 [type=greater_than, input_value=0.0, input_type=float]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/greater_than\n",
      "gender\n",
      "  Input should be 'male', 'female' or 'other' [type=enum, input_value='man', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/enum\n",
      "address.city\n",
      "  String should have at least 2 characters [type=string_too_short, input_value='京', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/string_too_short\n",
      "address.zipcode\n",
      "  String should match pattern '^\\d{6}$' [type=string_pattern_mismatch, input_value='12345', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/string_pattern_mismatch\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_87794/3495722627.py:59: PydanticDeprecatedSince212: Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. See the documentation at https://docs.pydantic.dev/2.12/concepts/validators/#model-after-validator. Deprecated in Pydantic V2.12 to be removed in V3.0.\n",
      "  def email_domain_check(cls, obj):\n",
      "/tmp/ipykernel_87794/3495722627.py:78: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.12/migration/\n",
      "  print(valid_data.dict())\n"
     ]
    }
   ],
   "source": [
    "from pydantic import BaseModel, Field, field_validator, model_validator\n",
    "from enum import Enum\n",
    "import re  # 用正则替代EmailStr验证\n",
    "\n",
    "# 枚举类型（用于字段可选值限制）\n",
    "class Gender(str, Enum):\n",
    "    MALE = \"male\"\n",
    "    FEMALE = \"female\"\n",
    "    OTHER = \"other\"\n",
    "\n",
    "# 嵌套模型（用于关联数据验证）\n",
    "class Address(BaseModel):\n",
    "    city: str = Field(min_length=2, description=\"城市名至少2字\")\n",
    "    zipcode: str = Field(pattern=r\"^\\d{6}$\", description=\"邮编必须6位数字\")\n",
    "\n",
    "# 核心验证类：集中所有验证场景（去掉EmailStr，用正则验证邮箱）\n",
    "class AllInOneValidation(BaseModel):\n",
    "    # 1. 基础字段验证（内置约束+Field参数）\n",
    "    username: str = Field(min_length=3, max_length=15, description=\"用户名3-15字\")\n",
    "    age: int = Field(ge=0, le=120, description=\"年龄0-120岁\")\n",
    "    email: str = Field(description=\"合法邮箱格式\")  # 改为普通字符串，用正则验证\n",
    "    score: float = Field(gt=0, lt=100, description=\"分数0<score<100（不包含0和100）\")\n",
    "    gender: Gender = Field(description=\"只能是枚举中的性别值\")\n",
    "    address: Address = Field(description=\"嵌套地址模型，需满足地址验证规则\")\n",
    "\n",
    "    # 2. 字段级预处理（mode=\"before\"：先转换再验证）\n",
    "    @field_validator(\"score\", mode=\"before\")\n",
    "    def round_score_to_one_decimal(cls, v):\n",
    "        \"\"\"分数预处理：保留1位小数\"\"\"\n",
    "        return round(float(v), 1)\n",
    "\n",
    "    # 3. 字段级自定义验证（补充邮箱正则验证）\n",
    "    @field_validator(\"email\")\n",
    "    def validate_email_format(cls, v):\n",
    "        \"\"\"用正则验证邮箱格式（替代EmailStr）\"\"\"\n",
    "        email_pattern = r\"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$\"\n",
    "        if not re.match(email_pattern, v):\n",
    "            raise ValueError(f\"邮箱格式无效：{v}\")\n",
    "        return v\n",
    "\n",
    "    # 4. 字段级自定义验证（单个字段复杂逻辑）\n",
    "    @field_validator(\"username\")\n",
    "    def username_no_special_chars(cls, v):\n",
    "        \"\"\"用户名不能包含特殊字符（仅字母/数字/下划线）\"\"\"\n",
    "        if not v.isalnum() and \"_\" not in v:\n",
    "            raise ValueError(\"用户名只能包含字母、数字或下划线\")\n",
    "        return v\n",
    "\n",
    "    # 5. 多字段联动验证（单个验证器处理多个字段）\n",
    "    @field_validator(\"age\", \"score\")\n",
    "    def value_not_zero(cls, v, field):\n",
    "        \"\"\"年龄和分数不能为0（补充基础约束的额外逻辑）\"\"\"\n",
    "        if v == 0:\n",
    "            raise ValueError(f\"{field.name}不能为0\")\n",
    "        return v\n",
    "\n",
    "    # 6. 模型级验证（跨字段联动，需所有字段先通过基础验证）\n",
    "    @model_validator(mode=\"after\")\n",
    "    def email_domain_check(cls, obj):\n",
    "        \"\"\"模型级联动：用户名不能和邮箱域名一致（示例逻辑）\"\"\"\n",
    "        email_domain = obj.email.split(\"@\")[-1]\n",
    "        if obj.username in email_domain:\n",
    "            raise ValueError(f\"用户名{obj.username}不能包含邮箱域名{email_domain}\")\n",
    "        return obj\n",
    "\n",
    "# ------------------------------\n",
    "# 测试：验证通过场景\n",
    "# ------------------------------\n",
    "valid_data = AllInOneValidation(\n",
    "    username=\"user_123\",\n",
    "    age=25,\n",
    "    email=\"user@example.com\",  # 合法邮箱格式\n",
    "    score=\"95.678\",  # 预处理为95.7（字符串转float+保留1位小数）\n",
    "    gender=\"male\",  # 符合枚举值\n",
    "    address={\"city\": \"北京\", \"zipcode\": \"100000\"}  # 嵌套模型验证通过\n",
    ")\n",
    "print(\"✅ 验证通过结果：\")\n",
    "print(valid_data.dict())\n",
    "\n",
    "# ------------------------------\n",
    "# 测试：验证失败场景（触发所有错误）\n",
    "# ------------------------------\n",
    "print(\"\\n❌ 验证失败示例（触发多类错误）：\")\n",
    "try:\n",
    "    invalid_data = AllInOneValidation(\n",
    "        username=\"u!\",  # 长度不够+含特殊字符\n",
    "        age=150,  # 超年龄上限\n",
    "        email=\"invalid-email\",  # 邮箱格式错误（正则不匹配）\n",
    "        score=0,  # 分数为0\n",
    "        gender=\"man\",  # 枚举值错误\n",
    "        address={\"city\": \"京\", \"zipcode\": \"12345\"}  # 城市名太短+邮编位数不够\n",
    "    )\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "adea442d",
   "metadata": {},
   "source": [
    "# 10. 业务字段举例(供参考)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "id": "c03b6a49",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel, ValidationError, field_validator, model_validator\n",
    "from datetime import datetime\n",
    "\n",
    "class UserProfile(BaseModel):\n",
    "    # 1. 基础信息\n",
    "    username: str          # 3-20位，只能包含字母/数字\n",
    "    email: str             # 必须包含@\n",
    "    phone: str             # 11位数字\n",
    "    # 2. 账号安全\n",
    "    password: str          # 至少8位，含数字和大写字母\n",
    "    is_verified: bool      # 必须为True（已实名认证）\n",
    "    # 3. 个人信息\n",
    "    age: int               # 18-120岁（成年用户）\n",
    "    birthday: str          # 格式YYYY-MM-DD，且年龄与生日匹配\n",
    "    gender: str            # 只能是\"男\"/\"女\"/\"保密\"\n",
    "    # 4. 其他\n",
    "    join_date: str         # 格式YYYY-MM-DD，不能晚于今天\n",
    "    balance: float         # 余额≥0\n",
    "\n",
    "    # 字段级校验：username\n",
    "    @field_validator(\"username\")\n",
    "    def check_username(cls, v):\n",
    "        if not (3 <= len(v) <= 20):\n",
    "            raise ValueError(\"用户名长度必须3-20位\")\n",
    "        if not v.isalnum():\n",
    "            raise ValueError(\"用户名只能包含字母和数字\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：email\n",
    "    @field_validator(\"email\")\n",
    "    def check_email(cls, v):\n",
    "        if \"@\" not in v:\n",
    "            raise ValueError(\"邮箱必须包含@\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：phone\n",
    "    @field_validator(\"phone\")\n",
    "    def check_phone(cls, v):\n",
    "        if not (v.isdigit() and len(v) == 11):\n",
    "            raise ValueError(\"手机号必须是11位数字\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：password\n",
    "    @field_validator(\"password\")\n",
    "    def check_password(cls, v):\n",
    "        if len(v) < 8:\n",
    "            raise ValueError(\"密码至少8位\")\n",
    "        if not any(c.isdigit() for c in v) or not any(c.isupper() for c in v):\n",
    "            raise ValueError(\"密码必须包含数字和大写字母\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：is_verified\n",
    "    @field_validator(\"is_verified\")\n",
    "    def check_verified(cls, v):\n",
    "        if not v:\n",
    "            raise ValueError(\"必须完成实名认证\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：age\n",
    "    @field_validator(\"age\")\n",
    "    def check_age(cls, v):\n",
    "        if not (18 <= v <= 120):\n",
    "            raise ValueError(\"年龄必须18-120岁（成年用户）\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：birthday\n",
    "    @field_validator(\"birthday\")\n",
    "    def check_birthday_format(cls, v):\n",
    "        try:\n",
    "            datetime.strptime(v, \"%Y-%m-%d\")\n",
    "        except ValueError:\n",
    "            raise ValueError(\"生日格式必须是YYYY-MM-DD\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：gender\n",
    "    @field_validator(\"gender\")\n",
    "    def check_gender(cls, v):\n",
    "        if v not in [\"男\", \"女\", \"保密\"]:\n",
    "            raise ValueError(\"性别只能是'男'/'女'/'保密'\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：join_date\n",
    "    @field_validator(\"join_date\")\n",
    "    def check_join_date(cls, v):\n",
    "        try:\n",
    "            join = datetime.strptime(v, \"%Y-%m-%d\")\n",
    "            if join > datetime.today():\n",
    "                raise ValueError(\"注册日期不能晚于今天\")\n",
    "        except ValueError:\n",
    "            raise ValueError(\"注册日期格式必须是YYYY-MM-DD\")\n",
    "        return v\n",
    "\n",
    "    # 字段级校验：balance\n",
    "    @field_validator(\"balance\")\n",
    "    def check_balance(cls, v):\n",
    "        if v < 0:\n",
    "            raise ValueError(\"余额不能为负数\")\n",
    "        return v\n",
    "\n",
    "    # 模型级校验：年龄与生日匹配（例如：2000-01-01出生，2024年年龄应为24岁）\n",
    "    @model_validator(mode=\"after\")\n",
    "    def check_age_birthday_match(self):\n",
    "        birth_year = int(self.birthday.split(\"-\")[0])\n",
    "        current_year = datetime.today().year\n",
    "        calculated_age = current_year - birth_year\n",
    "        # 允许±1岁误差（生日未到的情况）\n",
    "        if not (calculated_age - 1 <= self.age <= calculated_age + 1):\n",
    "            raise ValueError(f\"年龄与生日不匹配（根据生日计算应为{calculated_age}岁左右）\")\n",
    "        return self"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "id": "adc26e9f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ 校验通过！用户数据：\n",
      "{'username': 'Zhang3', 'email': 'zhang3@example.com', 'phone': '13812345678', 'password': 'Pass1234', 'is_verified': True, 'age': 24, 'birthday': '2000-05-10', 'gender': '男', 'join_date': '2023-01-15', 'balance': 100.5}\n"
     ]
    }
   ],
   "source": [
    "# 构造符合规则的数据\n",
    "valid_data = {\n",
    "    \"username\": \"Zhang3\",          # 3-20位字母数字\n",
    "    \"email\": \"zhang3@example.com\", # 含@\n",
    "    \"phone\": \"13812345678\",        # 11位数字\n",
    "    \"password\": \"Pass1234\",        # 8位+数字+大写\n",
    "    \"is_verified\": True,           # 已实名认证\n",
    "    \"age\": 24,                     # 18-120岁\n",
    "    \"birthday\": \"2000-05-10\",      # 格式正确，2000年出生→24岁（匹配age）\n",
    "    \"gender\": \"男\",                # 合法选项\n",
    "    \"join_date\": \"2023-01-15\",     # 不晚于今天（假设今天2025年）\n",
    "    \"balance\": 100.5               # 余额≥0\n",
    "}\n",
    "\n",
    "# 实例化模型（无异常）\n",
    "try:\n",
    "    user = UserProfile(**valid_data)\n",
    "    print(\"✅ 校验通过！用户数据：\")\n",
    "    print(user.model_dump())  # 输出所有字段\n",
    "except ValidationError as e:\n",
    "    print(\"❌ 校验失败：\")\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "id": "750dc8e6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "❌ 校验失败：\n",
      "10 validation errors for UserProfile\n",
      "username\n",
      "  Value error, 用户名长度必须3-20位 [type=value_error, input_value='张', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "email\n",
      "  Value error, 邮箱必须包含@ [type=value_error, input_value='zhang3.example.com', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "phone\n",
      "  Value error, 手机号必须是11位数字 [type=value_error, input_value='138abc', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "password\n",
      "  Value error, 密码至少8位 [type=value_error, input_value='123456', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "is_verified\n",
      "  Value error, 必须完成实名认证 [type=value_error, input_value=False, input_type=bool]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "age\n",
      "  Value error, 年龄必须18-120岁（成年用户） [type=value_error, input_value=17, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "birthday\n",
      "  Value error, 生日格式必须是YYYY-MM-DD [type=value_error, input_value='2000/05/10', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "gender\n",
      "  Value error, 性别只能是'男'/'女'/'保密' [type=value_error, input_value='保密哦', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "join_date\n",
      "  Value error, 注册日期格式必须是YYYY-MM-DD [type=value_error, input_value='2026-01-15', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n",
      "balance\n",
      "  Value error, 余额不能为负数 [type=value_error, input_value=-50, input_type=int]\n",
      "    For further information visit https://errors.pydantic.dev/2.12/v/value_error\n"
     ]
    }
   ],
   "source": [
    "# 构造含错误的数据（故意违反多个规则）\n",
    "invalid_data = {\n",
    "    \"username\": \"张\",              # 错误：长度不足3位\n",
    "    \"email\": \"zhang3.example.com\", # 错误：不含@\n",
    "    \"phone\": \"138abc\",             # 错误：非11位数字\n",
    "    \"password\": \"123456\",          # 错误：长度不足+无大写字母\n",
    "    \"is_verified\": False,          # 错误：未实名认证\n",
    "    \"age\": 17,                     # 错误：未满18岁\n",
    "    \"birthday\": \"2000/05/10\",      # 错误：日期格式错误（用了/）\n",
    "    \"gender\": \"保密哦\",            # 错误：性别选项非法\n",
    "    \"join_date\": \"2026-01-15\",     # 错误：注册日期晚于今天（2025年）\n",
    "    \"balance\": -50                 # 错误：余额为负数\n",
    "}\n",
    "\n",
    "# 实例化模型（触发异常）\n",
    "try:\n",
    "    user = UserProfile(** invalid_data)\n",
    "    print(\"✅ 校验通过！用户数据：\")\n",
    "    print(user.model_dump())\n",
    "except ValidationError as e:\n",
    "    print(\"❌ 校验失败：\")\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39996a45",
   "metadata": {},
   "source": [
    "# 为什么要这么做\n",
    "\n",
    "1. **集中暴露错误**：所有字段的错误会在实例化时一次性抛出，无需等到业务逻辑中逐个排查。  \n",
    "   例如，用户同时填错手机号、密码和生日，校验会同时提示这3个错误，用户可以一次性修改。\n",
    "\n",
    "2. **减少业务逻辑冗余**：如果不在模型层校验，需要在业务代码中为15个字段逐个写判断（如 `if len(username) <3: ...`），导致代码臃肿且易遗漏。\n",
    "\n",
    "3. **保障数据一致性**：通过模型级校验（如密码一致性、年龄与生日合理性），避免矛盾数据进入系统。\n",
    "\n",
    "4. **降低调试成本**：错误信息会明确指出哪个字段不符合规则（如「手机号必须是11位数字」），定位问题的效率比在业务逻辑中报 `TypeError` 高10倍。\n",
    "\n",
    "5. **适配多输入场景**：当这个类同时接收「前端表单提交」「第三方接口数据」「批量导入文件」时，一套校验规则可以覆盖所有输入来源，避免重复开发。\n",
    "\n",
    "\n",
    "** 总结 **\n",
    "字段越多，数据出错的「组合可能性」就越多（15个字段每个有3种错误，就有3^15种可能）。校验的本质是**用一套规则提前过滤所有无效组合**，让业务逻辑只处理「干净、可信」的数据，这在复杂模型中是不可或缺的。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-course (3.10.12)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
